#!/usr/bin/perl

####################################################
# Copyright (c) 2002-2007 BioWare Corp.
# http://www.bigdaddymonitor.org
#
# This file is part of the BigDaddy Monitoring
# program. It is free software distributed under
# the terms of the BSD License. It is ditributed
# in the hopes that it will be useful, but
# WITHOUT ANY WARRANTY; without event the implied
# warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
#
# The BSD License is available from:
# http://www.opensource.org/licenses/bsd-license.php
####################################################

#===================================================================================================
# BigDaddy External Monitor version 3.0
# Main Monitor for all Server Groups
# Dups 27.03.2004
# Dups 14.04.2006 <dups@bioware.com>
#===================================================================================================

BEGIN
{
	push(@INC,'/bigdaddy')
}


use strict;
use POSIX;
use DBI;
use Config::IniFiles;
use BioMain qw(:allfunctions);
use Time::Local;

my $logFile="/bigdaddy/logs/server_group_monitor.log";
my $processFile="/bigdaddy/logs/server_group_monitor.process";
check_logfile($logFile, "Server Group Main");

#Before we do anything we are going to bring up the main ini file: bigdaddy.ini should be in the
#same dir as these executables.
my $bigdaddy_cfg= new Config::IniFiles( -file => "/bigdaddy/bigdaddy.ini" );

my $this_process_name = $bigdaddy_cfg->val('main', 'thisprocessname');

my $dbIniHandle;

#Get the processing configuration
my $bdprocessing = $bigdaddy_cfg->val('main', 'bdprocessing');

#Get the dbini handle
#This is dependent on what kind of process is running
if ($bdprocessing eq "single" || $bdprocessing eq "multimaster")
{
	
	$dbIniHandle = $bigdaddy_cfg->val('main', 'dbinihandle');
}
elsif ($bdprocessing eq "masterslave")
{
	if ($bigdaddy_cfg->val('masterslave', 'thisinstance') eq "master")
	{
		$dbIniHandle = $bigdaddy_cfg->val('masterslave', 'masterdbhandle');
	}
	
	if ($bigdaddy_cfg->val('masterslave', 'thisinstance') eq "slave")
	{
		$dbIniHandle = $bigdaddy_cfg->val('masterslave', 'slavedbhandle');
	}
}



#Require the common Bigdaddy functions etc.
require ("/bigdaddy/bigdaddy_common.pl");


#Get the mailserver
my $mailServer = $bigdaddy_cfg->val('main', 'smtpserver');

if (!defined($mailServer) || $mailServer eq '')
{
	$mailServer = "localhost";
}

#Get the Global From Email
my $fromEmail = $bigdaddy_cfg->val('main', 'fromemail');



my ($start_val) = @ARGV;

#===================================================================================================
# Main
#===================================================================================================
if ($start_val eq "stop")
{
	system ("killall servergroup_monitor > /dev/null 2>&1");
	exit;

}




&daemonize;
#make a child

my $dbb1 = callfor_db_handle($dbIniHandle, 0, 99);
my $sql="UPDATE server_group SET last_monenabled=".time()." WHERE active=1";
$dbb1->do($sql);
$dbb1->disconnect;

#print "boo\n";

my $bReturn;
my $nScriptRunTime;

START: while(1)
{
	
	my $crontabIni="servermon";
	my $sID = "servergroupmon";
	my $nStartTime=time();
	
	#===================================================================================================
	# Processing configuration
	#===================================================================================================
	if ($bdprocessing ne "single")
	{
		if ($bdprocessing eq "masterslave")
		{
			$bReturn = CheckMasterSlave($bigdaddy_cfg, $sID);
		}
		
		if ($bdprocessing eq "multimaster")
		{
			$bReturn = CheckMultiMaster($bigdaddy_cfg, $dbIniHandle, $this_process_name, $sID);
		}
			
		if ($bReturn == 1)
		{
			#logEntry("Returned To Start", $logFile, 0);
			$nScriptRunTime = time() - $nStartTime;
			#Open a Log File and write in the current status on this server
			&SetProcessTime($processFile);
			sleep(60 - $nScriptRunTime);
			goto START;
		}
		
		if ($bReturn == 2)
		{
			#logEntry("Continuing", $logFile, 0);
			goto CONTINUE;
		}

	}
	
	CONTINUE: 
	
	my $dbb = callfor_db_handle($dbIniHandle, 0, 99);
	
	#Database Reporting
	my $sql="UPDATE report SET report='".time()."', processID = '".$this_process_name."' WHERE ID='".$sID."'";
    #print $sql."\n";
	$dbb->do( $sql );
	
	#Open a Log File and write in the current status on this server
	&SetProcessTime($processFile);
	

	$sql="SELECT inifile, ID FROM inifile WHERE ID IN ('".$crontabIni."')";
	#print $sql."\n";
	my $result=$dbb->prepare( $sql );
	$result->execute;

	while (my $ref=$result->fetchrow_hashref())
	{
	    open (TMPINIFILE, ">/tmp/exmon".$ref->{'ID'}.".ini") or die "Could not open temp file for writing: $!";
	    print TMPINIFILE $ref->{'inifile'};
	    close (TMPINIFILE) or die "Could not close temp file: $!";
	}

	my $cfg_crontab= new Config::IniFiles( -file => "/tmp/exmon".$crontabIni.".ini" );

	# Check for the crontab delay value
	my $delay=$cfg_crontab->val('global','globaldelay');
	my $status=$cfg_crontab->val('global','globalstatus');




	if ($status eq "off" || ($status eq "delay" && $delay>time()))
	{
		#continue don't start up monitoring
		#Do any processing of off stuff
	}
	else
	{
		$sql="SELECT ID FROM server_group WHERE active=1";
		my $result=$dbb->prepare($sql);
		$result->execute;

		while (my $ref=$result->fetchrow_hashref())
		{
			&makeNewChild($ref->{'ID'});
		}
	}

	#We only want to run this a minimum of every 60 seconds.
	$nScriptRunTime=time()-$nStartTime;

	#Update the report table.

	$dbb->disconnect;
	if ($nScriptRunTime<60)
	{
		sleep(60-$nScriptRunTime);
	}
}

#===================================================================================================
# Child Subroutine
# Forks child for each server
#===================================================================================================
sub makeNewChild
{

	my ($serverGroupID) = @_;
	my $sql;
	my $pid;
	#my $logFile="/bigdaddy/logs/server_group_monitor.log";
	#check_logfile($logFile, "Server Group Child");
	die "fork: $!" unless defined ($pid = fork);

	if ($pid)
	{
		#parent process records the birth of the child and returns to the rest of the script
		#$children[$number]=$pid;
		#$SIG{CHLD} = 'IGNORE';
		#return;

		my $kid;
		do {
			$kid = waitpid($pid, &WNOHANG);
		} until $kid > 0;
		return;
	}
	else
	{
		#First to check if the servers are being monitored at all....
		#Create the database handle with no idle and no crash on errors
		my $dbh = callfor_db_handle($dbIniHandle, 0, 99);


		#Get the Phone Tags
		my %phoneTags;
		%phoneTags = get_phoneTags($dbh);

		$sql="SELECT * FROM server_group WHERE ID=".$serverGroupID;
		#print $sql."\n";
		my $result=$dbh->prepare($sql);
		$result->execute;

		my $delaystatus=0;

		while (my $ref=$result->fetchrow_hashref())
		{
			my $cfg=get_inicfg( $ref->{'iniID'}, $serverGroupID, $dbh );

			#Check to see if this is a time for the server_group not to be monitored.
			#if so simply set it delayed and quickly escape out of here.
			my $delaystart=$cfg->val('global','tempdelaystart');
			my $delayend=$cfg->val('global','tempdelayend');


			if ($delaystart ne '' && $delayend ne '')
			{

				$delaystatus=checkTempDelay($delaystart, $delayend);

				if ($delaystatus>0)
				{
					$sql="UPDATE server_group SET active=2, delayed_until='".$delaystatus."'  WHERE ID='".$serverGroupID."'";
					$dbh->do($sql);
					goto DONE;
				}
			}
			
			#Check to see how many of the server modules added into this server_group are having problems
			#First what is the number of server modules total
			$sql = "SELECT count(*) as total_modules
                FROM server_group_map sgm
                LEFT JOIN server_module sm ON sm.ID = sgm.server_moduleID
                WHERE sgm.server_groupID='".$serverGroupID."'
                AND sm.active=1";
			my $result2=$dbh->prepare($sql);
			$result2->execute;
			
			my $nTotalModules = 0;

			while (my $ref2=$result2->fetchrow_hashref())
			{
				$nTotalModules = $ref2->{'total_modules'};
                #print $nTotalModules."\n";
			}
			
			#Now count the number having problems
			$sql = "SELECT count(*) as module_problems
				FROM server_group_map sgm
				LEFT JOIN server_module sm ON sm.ID = sgm.server_moduleID
				WHERE sgm.server_groupID='".$serverGroupID."'
					AND sm.mon_currentlevel > 0
					AND sm.active = 1";
					
			$result2=$dbh->prepare($sql);
			$result2->execute;
			
			my $nProblemModules = 0;
            
            while (my $ref2=$result2->fetchrow_hashref())
			{
				$nProblemModules = $ref2->{'module_problems'};
                #print $nProblemModules."\n";
			}
            
            #If there are no problems just continue on
            if ($nProblemModules == 0)
            {
                &resetinfo( $serverGroupID, $dbh );
                goto DONE;
            }
            
            my $bProblem = 0;
            
            #is there a percentage threshhold
            if ($ref->{'mon_percentthreshold'} > 0 && $nTotalModules > 0)
            {
                my $nPercentProblem = ($nProblemModules / $nTotalModules) * 100;
                
                if ($nPercentProblem >= $ref->{'mon_percentthreshold'})
                {
                    $bProblem = 1;
                }
            }
            
            if ($ref->{'mon_numberthreshold'} > 0 && $bProblem == 0)
            {
                if ($nProblemModules >= $ref->{'mon_numberthreshold'})
                {
                    $bProblem = 1;
                }
            }
            
            #print "Problem: ".$bProblem."\n";
            
            #is there a problem?
            if ($bProblem == 0)
            {
                &resetinfo( $serverGroupID, $dbh );
                goto DONE;
            }
			
            #We know now that this Server Group is having a problem.
            #We need to get the ini that describes the actions
            
            # Is the currentlevel 0?
			if ($ref->{'mon_currentlevel'}==0)
			{
                &doaction( $ref->{'iniID'}, $serverGroupID, $ref->{'groupname'}, $ref->{'mon_startlevel'}, $dbh, %phoneTags);
                &updateinfo( $serverGroupID, $ref->{'mon_startlevel'}, $dbh );
                goto DONE;
			}

            # If the currentlevel is greater than 0
            if ($ref->{'mon_currentlevel'}>0)
            {
                #is this the final level?
                if ($ref->{'mon_currentlevel'}==5 || $ref->{'mon_currentlevel'}==$ref->{'mon_endlevel'})
                {
                    #is it time to loop
                    if ((time()-600)>$ref->{'mon_lastcheck'})
                    {
                        &doaction( $ref->{'iniID'}, $serverGroupID, $ref->{'groupname'}, $ref->{'mon_currentlevel'}, $dbh, %phoneTags);
                        &setlastcheck($serverGroupID, $dbh);
                    }
                }
                else
                {
                    #It's not the final level so check to see if it is time to escalate
                    my $level="level".$ref->{'mon_currentlevel'};
                    my $escalateTime=0;
                    my $loopTime=0;

                    if (defined($cfg->val( $level, 'escalate' )))
                    {
                        $escalateTime=$cfg->val( $level, 'escalate' );
                    }

                    if (defined($cfg->val( $level, 'loop' )))
                    {
                        $loopTime=$cfg->val( $level, 'loop' );
                    }

                    #&logEntry("Loop: $loopTime Escalate: $escalateTime", $logFile, 0);

                    if ((time()-$escalateTime)>$ref->{'mon_currentlevelstart'})
                    {
                        #Time to escalate
                        &doaction( $ref->{'iniID'}, $serverGroupID, $ref->{'groupname'}, ($ref->{'mon_currentlevel'}+1), $dbh, %phoneTags);
                        &updateinfo( $serverGroupID, ($ref->{'mon_currentlevel'}+1), $dbh );
                    }
                    elsif ((time()-$loopTime)>$ref->{'mon_lastcheck'})
                    {
                        #&logEntry("I say again: Loop: $loopTime Escalate: $escalateTime", $logFile, 0);
                        #Time to Loop
                        &doaction( $ref->{'iniID'}, $serverGroupID, $ref->{'groupname'}, $ref->{'mon_currentlevel'}, $dbh, %phoneTags);
                        &setlastcheck($serverGroupID, $dbh);
                    }
                }
            }
			else
			{
				$sql="UPDATE server_group SET mon_currentlevel=0, mon_currentlevelstart=0, mon_lastcheck=0 WHERE ID=".$serverGroupID;
				#print $sql."\n";
				$dbh->do( $sql );
			}
		} #end while
	
		DONE: exit;
	} #end if
} #end sub


#===================================================================================================
# resetinfo(int $serverGroupID, dbh $dbh)
# Updates server info and resets a server group
#===================================================================================================
sub resetinfo
{
	my ($serverGroupID, $dbh)=@_;
    
	$sql="UPDATE server_group SET mon_currentlevel=0, mon_currentlevelstart=0, mon_lastcheck=0 WHERE ID=".$serverGroupID;
	#print $sql."\n";
	$dbh->do( $sql );
}

#===================================================================================================
# updateinfo(int $serverGroupID, dbh $dbj, int $levelup)
# Updates the info for a server_group to go up a level, sets the time for the level start
# And the lastcheck value which tracks when an action was last performed
#===================================================================================================
sub updateinfo
{
	my ($serverGroupID, $levelup, $dbh)=@_;
    
	my $sql="UPDATE server_group SET mon_currentlevel='".$levelup."', mon_currentlevelstart=".time().", mon_lastcheck=".time()." WHERE ID=".$serverGroupID;
    #print $sql."\n";
	$dbh->do( $sql );
}

#===================================================================================================
# setlastcheck(int $serverGroupID, dbh $dbh)
# Sets only the last check value of when actions were performed for server Group ID
#===================================================================================================
sub setlastcheck
{
	my ($serverGroupID, $dbh) = @_;
	my $sql="UPDATE server_group SET mon_lastcheck=".time()." WHERE ID=".$serverGroupID;
    #print $sql."\n";
	$dbh->do( $sql );
}

#===================================================================================================
# doaction(str iniID, int $serverGroupID, str $hostName, int $level, dbh $dbh, %phoneTags)
# So the actions specified in the ini for this level, this servergroup
#===================================================================================================
sub doaction
{
	my ($iniID, $serverGroupID, $serverGroupName, $level, $dbh, %phoneTags) = @_;
	#Get the current time string
	my $cfg=get_inicfg( $iniID, $serverGroupID, $dbh );
	
	my $sTimeString = localtime;
	
	my $bUseTags;
	#Check to see if this ini file uses tags or email addresses
	if (defined($cfg->val( 'global','usetags' )) && $cfg->val( 'global','usetags' ) eq "yes")
	{
		$bUseTags = 1;
	}

	#print $cfg->val( "level".$level,'email' )."\n";
	if (defined($cfg->val( "level".$level,'email' )))
	{
		my $messageString="Server Group ".$serverGroupName." Failing / Level: ".$level."\n";
		if (defined($cfg->val( "level".$level,'message' )))
		{
			$messageString.=$cfg->val( "level".$level,'message' );
		}
		$messageString.="\n".$sTimeString;
		my $subjectString = "BD".$level." - ServerGroup: ".$serverGroupName;
		logEntry("Action Performed: ".$subjectString, $logFile, 0);
		
		my @emailTags;
		
		if ($bUseTags == 1)
		{
			@emailTags = split(/,/, $cfg->val( "level".$level,'email' ));
			
			for (my $i = 0; $i<scalar(@emailTags); $i++)
			{
				my $sEmail = $phoneTags{trim($emailTags[$i])};
				&send_mail($phoneTags{$emailTags[$i]}, $messageString, $subjectString, $fromEmail, $mailServer  );
			}
		}
		else
		{
			&send_mail($cfg->val( "level".$level,'email' ), $messageString, $subjectString, $fromEmail, $mailServer  );
		}
	}
}
#===================================================================================================
# get_phoneTags
# Gets the phone tags and places them into a hash
#===================================================================================================
sub get_phoneTags
{
	my ($dbh) = @_;
	my %hash;
	
	my $sql="SELECT ID, phoneNumber FROM phoneTag";
	my $result=$dbh->prepare( $sql );
	$result->execute;
	while (my $ref=$result->fetchrow_hashref())
	{
		$hash{$ref->{'ID'}} = $ref->{'phoneNumber'};
	}
	
	return %hash;
}



#===================================================================================================
# get_inicfg(str iniID, int cronID, dbh dbh)
# Gets the inifile for that cron
#===================================================================================================
sub get_inicfg
{
	my ($iniID, $serverGroupID, $dbh) = @_;
	my $sql="SELECT inifile, ID FROM inifile WHERE ID='".$iniID."'";
	my $result=$dbh->prepare( $sql );
	$result->execute;

	my $filenameAdd="serverGroupID".$serverGroupID."_".time();
	my $inifileName;
	while (my $ref=$result->fetchrow_hashref())
	{
		$inifileName="/tmp/".$ref->{'ID'}.".ini.".$filenameAdd;
		open (TMPINIFILE, ">$inifileName") or die "Could not open temp file for writing: $!";
		print TMPINIFILE $ref->{'inifile'};
		close (TMPINIFILE) or die "Could not close temp file: $!";
	}

	my $cfg= new Config::IniFiles( -file => $inifileName );
	unlink($inifileName);
	return $cfg;
}

#===================================================================================================
# checkTempDelay(str delaystart, str delayend)
# Checks to see if current time is between the temp delays spec
#===================================================================================================
sub checkTempDelay
{
	my ($delaystart, $delayend) = @_;

	my $starthour;
	my $startmin;
	my $endhour;
	my $endmin;

	if ($delaystart =~ m/([0-9]+):([0-9]+)/)
	{
		$starthour=$1;
		$startmin=$2;
	}

	if ($delayend =~ m/([0-9]+):([0-9]+)/)
	{
		$endhour=$1;
		$endmin=$2;
	}

	my $startstamp= timelocal(0,$startmin,$starthour,(localtime)[3,4,5]);
	my $endstamp= timelocal(0,$endmin,$endhour,(localtime)[3,4,5]);

	my $currentTime=time();
	if ($currentTime>$startstamp && $currentTime<$endstamp)
	{
		return $endstamp;
	}
	else
	{
		return 0;
	}
}





